package engineer.reverse.schema;

import com.google.common.collect.Lists;
import engineer.reverse.Revert;
import entity_model.Entity;
import entity_model.EntityCreationListener;
import entity_model.EntityModel;
import javafx.stage.FileChooser;
import utils.titles.Title;
import mysql.Column;
import mysql.Schema;
import mysql.Table;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static engineer.TypeMapper.toDataType;
import static entity_model.Relationship.Role.Cardinality.MANY;
import static entity_model.Relationship.Role.Cardinality.ONE;
import static mysql.Column.Key.MUL;

/**
 * Created by nuno-rafael-figueiredo on 09-05-2014.
 */
public class RevertSchema extends Revert {
    private static final List<String> META_COLUMNS = Lists.newArrayList("created_at", "updated_at", "deleted_at");

    private final Map<Table, Entity> generated = new LinkedHashMap<>();
    private Schema schema;

    public RevertSchema(Schema schema) {
        this.schema = schema;
    }

    @Override
    public void execute() {
        setEntityModel(new EntityModel(schema.getTitle()));

        for (Table table : schema.getTables().getList())
            createEntity(getEntityModel(), table);
    }

    private void createEntity(EntityModel entityModel, Table table) {
        final Entity entity = entityModel.getEntity(table.getTitle());

        int numDependencyTables = table.getDependencyTables().length;

        if (numDependencyTables == 2){
            on(table.getDependencyTables()[0], left -> on(table.getDependencyTables()[1], right -> entityModel.createRelationship(
                    entity,
                    MANY, left,
                    MANY, right
            )));
        }

        List<String> metaColumns = new ArrayList<>(META_COLUMNS);
        for (Column column : table.getColumnList()) {

            if (metaColumns.contains(column.getName())) {
                metaColumns.remove(column.getName());
                continue;
            }

            Column.Reference reference = column.getReference();

            if (column.isPrimary()) {
                if (numDependencyTables == 1)
                    on(reference.getTable(), entity::setBaseEntity);

                continue;
            }

            if (reference != null) {
                Title title;
                try {
                    title = column.getTitle().cutLast();
                } catch (Exception e) {
                    throw new RuntimeException(table + "." + column, e);
                }

                Table referencedTable = column.getReference().getTable();
                on(referencedTable, referencedEntity -> entityModel.createRelationship(
                        column.getKey() == MUL ? MANY : ONE,
                        entity,
                        referencedEntity,
                        title
                ));

                continue;
            }

            entity.createAttribute(toDataType(column.getType()), column.getTitle());
        }

        trigger(table, entity);
    }

    private void trigger(Table table, Entity entity) {
        generated.put(table, entity);
        if (Listeners.containsKey(table))
            for (EntityCreationListener listener : Listeners.get(table))
                listener.created(entity);
    }

    private final Map<Table, List<EntityCreationListener>> Listeners = new LinkedHashMap<>();

    private void on(Table table, EntityCreationListener listener) {
        if (generated.containsKey(table)) {
            listener.created(generated.get(table));
            return;
        }

        if (!Listeners.containsKey(table))
            Listeners.put(table, new ArrayList<>());

        Listeners.get(table).add(listener);
    }

    public static class ExtensionFilters {
        public static final FileChooser.ExtensionFilter EEM = new FileChooser.ExtensionFilter("Engineer Entity Model", "*.eem.json");
    }
}
